package com.yangjicong.project.redis;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yangjicong.project.common.FastJsonUtils;
import com.yangjicong.project.common.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.support.atomic.RedisAtomicInteger;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import static com.sun.corba.se.spi.activation.IIOP_CLEAR_TEXT.value;

/**
 * @author 旧巷里的少年郎
 */
@Slf4j
@Component
public class RedisUtils {

    @Autowired(required = false)
    private RedisTemplate<String, Serializable> redisTemplate;

    @Autowired(required = false)
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 缓存放入并设置时间
     *
     * @param key      键
     * @param object   值
     * @param time     time要大于0 如果time小于等于0 将设置无限期
     * @param timeUnit 时间单位
     * @return true成功 false 失败
     */
    public boolean set(String key, Object object, long time, TimeUnit timeUnit) {
        try {
            if (Objects.isNull(object)) {
                log.info("插入 redis 值为空");
                return false;
            }
            final String value = toJSONString(object);
            boolean result = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
                byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
                byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
                connection.set(redisKey, redisValue);
                if (time > 0) {
                    stringRedisTemplate.expire(key, time, timeUnit);
                } else {
                    stringRedisTemplate.opsForValue().set(key, value);
                }
                return true;
            });
            return result;
        } catch (Exception e) {
            log.error("插入redis异常，异常信息为：{}", e);
        }
        return false;
    }

    /**
     * redis 值如果是对象转成json,如果是字符串不变
     *
     * @param obj 值
     * @return 值
     */
    public String toJSONString(Object obj) {
        final String value;
        if (obj instanceof String) {
            value = String.valueOf(obj);
        } else {
            value = JSONObject.toJSONString(obj);
        }
        return value;
    }

    public boolean set(String key, Object value) {
        return set(key ,value ,-1);
    }

    /**
     * 缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        return set(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public String get(String key) {
        try {
            if (StringUtils.isBlank(key)) {
                log.info("查询redis key 为空");
                return StringUtils.EMPTY;
            }
            String resultStr = (String) stringRedisTemplate.execute((RedisCallback<Object>) connection -> {
                byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
                byte[] value = connection.get(redisKey);
                return stringRedisTemplate.getStringSerializer().deserialize(value);
            });
            return resultStr;
        } catch (Exception e) {
            log.error("查询redis异常，异常信息为：{}", e);
        }
        return StringUtils.EMPTY;
    }


    /**
     * 获取缓存list,存入的为json对象
     *
     * @param key   键
     * @param clazz 转换的json class
     * @param <T>
     * @return
     */
    public <T> List<T> getJsonToList(String key, Class<T> clazz) {
        String jsonValue = get(key);

        if (Objects.isNull(jsonValue)) {
            return Collections.emptyList();
        }
        try {
            return JSON.parseArray(jsonValue, clazz);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Collections.emptyList();
        }
    }

    /**
     * 获取缓存对象
     *
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T getJsonToObject(String key, Class<T> clazz) {
        String jsonValue = get(key);

        if (Objects.isNull(jsonValue)) {
            return null;
        }
        try {
            return FastJsonUtils.toObject(jsonValue, clazz);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }


    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (ArrayUtils.isEmpty(key)) {
            log.warn("RedisUtils del cache by keys. keys is empty!");
            return;
        }
        try {
            if (key.length > 1) {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
                return;
            }
            redisTemplate.delete(key[0]);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 获取缓存对象
     *
     * @param key       缓存key
     * @param seconds   缓存时间 ：秒
     * @param dbHandler DB数据
     * @param clazz     类型
     * @return
     */
    public <T> T getCacheObjectDbHandler(String key, long seconds, Supplier<T> dbHandler, Class<T> clazz) {
        log.debug("call[RedisUtils] [getCacheObjectDbHandler] redis key :[{}]", key);

        //取缓存有直接返回
        String value = get(key);
        log.debug("call[RedisUtils] [getCacheObjectDbHandler] redis value :[{}]", value);
        if (StringUtils.isNotEmpty(value)) {
            return JSON.parseObject(value, clazz);
        }

        T t;
        try {
            //获取对象
            t = dbHandler.get();
            log.debug("call[RedisUtils] [getCacheObjectDbHandler] db value :[{}]", t);
            if (Objects.isNull(t)) {
                return null;
            }
        } catch (Exception e) {
            log.error("call[RedisUtils] [getCacheObjectDbHandler] key :[{}] Exception :[{}]", key, e);
            return null;
        }
        //插入缓存
        boolean redisResult = set(key, t, seconds);
        log.debug("call[RedisUtils] [getCacheObjectDbHandler] insert value to redis. key :[{}]  value :[{}] redisResult :[{}]", key, t, redisResult);
        return t;

    }

    /**
     * 获取缓存对象
     *
     * @param key       缓存key
     * @param seconds   缓存时间 ：秒
     * @param dbHandler DB数据
     * @param clazz     类型
     * @return
     */
    public <T> List<T> getCacheListDbHandler(String key, long seconds, Supplier<List<T>> dbHandler, Class<T> clazz) {
        log.debug("call[RedisUtils] [getCacheListDbHandler] redis key :[{}]", key);

        //取缓存有直接返回
        String value = get(key);
        log.debug("call[RedisUtils] [getCacheListDbHandler] redis value :[{}]", value);
        if (StringUtils.isNotEmpty(value)) {
            return JSON.parseArray(value, clazz);
        }

        List<T> list;
        try {
            //获取对象
            list = dbHandler.get();
            log.debug("call[RedisUtils] [getCacheListDbHandler] db value :[{}]", list);
            if (CollectionUtils.isEmpty(list)) {
                return null;
            }
        } catch (Exception e) {
            log.error("call[RedisUtils] [getCacheListDbHandler] key :[{}] Exception :[{}]", key, e);
            return null;
        }
        //插入缓存
        boolean redisResult = set(key, list, seconds);
        log.debug("call[RedisUtils] [getCacheListDbHandler] insert value to redis. key :[{}]  value :[{}] redisResult :[{}]", key, list, redisResult);

        return list;

    }

    /**
     * 获取自增序列号 ，从0开始
     *
     * @param key    redis key
     * @param expire 过期时间
     * @param unit   单位
     * @return 自增的结果
     */
    public long getIncr(String key, long expire, TimeUnit unit) {
        RedisAtomicInteger atomicInteger = new RedisAtomicInteger(key, redisTemplate.getConnectionFactory());
        atomicInteger.expire(expire, unit);
        return atomicInteger.getAndIncrement();

    }

    /**
     * 自增序列的当前值
     */
    public long getCount(String key) {
        RedisAtomicInteger counter = new RedisAtomicInteger(key, redisTemplate.getConnectionFactory());
        return counter.get();
    }

    /**
     * spring 容器中获取RedisUtils 实例
     * 提供静态获取实例的方式，方便调用
     *
     * @return
     */
    public static RedisUtils getInstance() {
        return SpringContextHolder.getBean(RedisUtils.class);
    }

    /**
     * 原子操作
     *
     * @param key
     * @param value
     */
    public void setNx(String key, String value, long time) {
        stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            if (time > 0) {
                connection.setEx(redisKey, time, redisValue);
            }
            return true;
        });
    }

    /**
     * zSet
     *
     * @param key
     * @param value
     */
    public void zAddIncr(String key, String value, long score) {
        zAdd(key, value, zScore(key, value) + score);
    }

    /**
     * zSet
     *
     * @param key
     * @param value
     */
    public void zAdd(String key, String value) {
        zAdd(key, value, 1);
    }

    /**
     * zSet
     *
     * @param key
     * @param value
     */
    public void zAdd(String key, String value, long score) {
        stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            return connection.zAdd(redisKey, score, redisValue);
        });
    }

    /**
     * zCount
     *
     * @param key
     * @param min
     * @param max
     */
    public Long zCount(String key, double min, double max) {
        Long execute = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            return connection.zCount(redisKey, min, max);
        });
        return Optional.ofNullable(execute).orElse(0L).longValue();
    }

    /**
     * zCount
     *
     * @param key
     */
    public Long zCount(String key) {
        return zCount(key, 0, Double.MAX_VALUE);
    }


    public Cursor<RedisZSetCommands.Tuple> zScan(String key) {
        Cursor<RedisZSetCommands.Tuple> execute = stringRedisTemplate.execute((RedisCallback<Cursor<RedisZSetCommands.Tuple>>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            Cursor<RedisZSetCommands.Tuple> tupleCursor;
            int cursor = 1;
            do {
                ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions();
                scanOptionsBuilder.count(cursor);
                scanOptionsBuilder.match(key);
                tupleCursor = connection.zScan(redisKey, ScanOptions.NONE);
            } while (tupleCursor != null && tupleCursor.getCursorId() != 0);
            return tupleCursor;
        });
        return Optional.ofNullable(execute).orElse(null);
    }

    /**
     * zRem
     *
     * @param keys
     * @return
     */
    public Long zRem(String... keys) {
        Long execute = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {
            long count = 0;
            for (String key : keys) {
                byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
                if (connection.zCount(redisKey, 0, Long.MAX_VALUE) == 0) {
                    continue;
                }
                log.info("zRem key：{}", key);
                count += connection.zRemRange(redisKey, 0, Long.MAX_VALUE);
            }
            return count;
        });
        return Optional.ofNullable(execute).orElse(0L).longValue();
    }

    /**
     * zRem
     *
     * @param key
     * @param value
     * @return
     */
    public Long zRem(String key, String value) {
        Long execute = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            if (connection.zScore(redisKey, redisValue) == null) {
                return 0L;
            }
            log.info("zRem key：{}，value：{}", redisKey, redisValue);
            return connection.zRem(redisKey, redisValue);
        });
        return Optional.ofNullable(execute).orElse(0L).longValue();
    }

    /**
     * zScore
     *
     * @param key
     * @param value
     * @return
     */
    public Long zScore(String key, String value) {
        Double execute = stringRedisTemplate.execute((RedisCallback<Double>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            return connection.zScore(redisKey, redisValue);
        });
        return Optional.ofNullable(execute).orElse(0d).longValue();
    }

    /**
     * zRange
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<byte[]> zRange(String key, int min, int max) {
        Set<byte[]> execute = stringRedisTemplate.execute((RedisCallback<Set<byte[]>>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            return connection.zRange(redisKey, min, max);
        });
        return execute;
    }

    /**
     * zRevRange
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<RedisZSetCommands.Tuple> zRevRange(String key, long min, long max) {
        Set<RedisZSetCommands.Tuple> execute = stringRedisTemplate.execute((RedisCallback<Set<RedisZSetCommands.Tuple>>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            return connection.zRevRangeWithScores(redisKey, min, max);
        });
        return execute;
    }

    /**
     * zIncrBy
     *
     * @param key
     * @param value
     * @return
     */
    public Long zIncrBy(String key, String value, long score) {
        Double execute = stringRedisTemplate.execute((RedisCallback<Double>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
            return connection.zIncrBy(redisKey, score, redisValue);
        });
        return Optional.ofNullable(execute).orElse(0d).longValue();
    }

    /**
     * pfAdd
     *
     * @param key
     * @param values
     * @return
     */
    public Long pfAdd(String key, String... values) {
        Long execute = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {
            long count = 0;
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            for (String value : values) {
                byte[] redisValue = stringRedisTemplate.getStringSerializer().serialize(value);
                count += connection.pfAdd(redisKey, redisValue);
            }
            return count;
        });
        return Optional.ofNullable(execute).orElse(0L).longValue();
    }

    /**
     * pfCount
     *
     * @param key
     * @return
     */
    public Long pfCount(String key) {
        Long execute = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] redisKey = stringRedisTemplate.getStringSerializer().serialize(key);
            return connection.pfCount(redisKey);
        });
        return Optional.ofNullable(execute).orElse(0L).longValue();
    }

    /**
     * incr 递增
     *
     * @param key   key
     * @param delta delta
     * @return boolean
     * @author 张凯
     * @date 2022/2/23 16:38
     */
    public boolean incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return stringRedisTemplate.opsForValue().increment(key, delta) > 0;
    }

    /**
     * decr 递减
     *
     * @param key   key
     * @param delta delta
     * @return boolean
     * @author 张凯
     * @date 2022/2/23 16:38
     */
    public boolean decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return stringRedisTemplate.opsForValue().increment(key, -delta) > 0;
    }
}
